// Copyright (C) 2025, MinIO, Inc.
//
// This code is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License, version 3,
// as published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License, version 3,
// along with this program.  If not, see <http://www.gnu.org/licenses/>

package controller

import (
	"context"
	"fmt"
	"testing"
	"time"

	"github.com/minio/operator/pkg/resources/configmaps"
	v1 "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1"
	"github.com/prometheus-operator/prometheus-operator/pkg/client/versioned/fake"
	"gopkg.in/yaml.v2"
	v3 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	fake3 "k8s.io/client-go/kubernetes/fake"

	miniov2 "github.com/minio/operator/pkg/apis/minio.min.io/v2"
)

func Test_checkAndCreatePrometheusAddlConfig(t *testing.T) {
	type expect struct {
		afterScrapeNumber  int
		afterScrapePath    []string
		afterScrapeName    []string
		changedTokenNumber int
	}
	type arg struct {
		accessKey       string
		secretKey       string
		tenantName      string
		tenantNamespace string
		beforeScrapes   []configmaps.ScrapeConfig
		paths           []string
		expects         expect
	}
	type args struct {
		name string
		arg  arg
	}

	tenantAName := "tenantA"
	tenantANamespace := "tenantA-ns"
	tenantBName := "tenantB"
	tenantBNamespace := "tenantB-ns"

	// Next is to emulate a tenant with same name as tenantA (different namespace)
	tenantCName := tenantAName
	tenantCNamespace := "tenantC-ns"

	testRunCheckAndCreatePrometheusAddlConfig := func(t *testing.T, arg arg) {
		beforeScrapesBytes, err := yaml.Marshal(arg.beforeScrapes)
		if err != nil {
			t.Fatal(err)
		}
		testNS := arg.tenantNamespace
		prometheus := &v1.Prometheus{
			ObjectMeta: metav1.ObjectMeta{
				Name:      "testPrometheus",
				Namespace: "default",
			},
			Spec: v1.PrometheusSpec{
				CommonPrometheusFields: v1.CommonPrometheusFields{
					AdditionalScrapeConfigs: &v3.SecretKeySelector{
						Key: "",
						LocalObjectReference: v3.LocalObjectReference{
							Name: miniov2.PrometheusAddlScrapeConfigSecret,
						},
					},
				},
			},
		}
		beforeSecret := &v3.Secret{
			ObjectMeta: metav1.ObjectMeta{
				Name:      miniov2.PrometheusAddlScrapeConfigSecret,
				Namespace: "default",
			},
			Data: map[string][]byte{
				miniov2.PrometheusAddlScrapeConfigKey: beforeScrapesBytes,
			},
		}
		kubeclient := fake3.NewSimpleClientset(beforeSecret)
		controller := Controller{
			promClient:    fake.NewSimpleClientset(prometheus),
			kubeClientSet: kubeclient,
		}
		tenant := &miniov2.Tenant{
			ObjectMeta: metav1.ObjectMeta{
				Name:      arg.tenantName,
				Namespace: testNS,
			},
			Spec: miniov2.TenantSpec{
				PrometheusOperatorScrapeMetricsPaths: arg.paths,
			},
		}
		// bearer_token generated by unix timestamp
		time.Sleep(time.Second)
		err = controller.checkAndCreatePrometheusAddlConfig(context.Background(), tenant, arg.accessKey, arg.secretKey)
		if err != nil {
			t.Fatal(err)
		}
		afterSecret, err := kubeclient.CoreV1().Secrets("default").Get(context.Background(), miniov2.PrometheusAddlScrapeConfigSecret, metav1.GetOptions{})
		if err != nil {
			t.Fatal(err)
		}
		var afterScrapes []configmaps.ScrapeConfig
		err = yaml.Unmarshal(afterSecret.Data[miniov2.PrometheusAddlScrapeConfigKey], &afterScrapes)
		if err != nil {
			t.Fatal(err)
		}
		if len(afterScrapes) != arg.expects.afterScrapeNumber {
			t.Fatalf("Expected %d scrape configs, got %d", arg.expects.afterScrapeNumber, len(afterScrapes))
		}
		for i, scrape := range afterScrapes {
			if scrape.JobName != arg.expects.afterScrapeName[i] {
				t.Fatalf("Expected scrape config name %s, got %s", arg.expects.afterScrapeName[i], scrape.JobName)
			}
			if scrape.MetricsPath != arg.expects.afterScrapePath[i] {
				t.Fatalf("Expected scrape config path %s, got %s", arg.expects.afterScrapePath[i], scrape.MetricsPath)
			}
		}
		changedTokenNumber := 0
		for _, afterScrape := range afterScrapes {
			for _, scrape := range arg.beforeScrapes {
				if scrape.JobName == afterScrape.JobName {
					if scrape.BearerToken != afterScrape.BearerToken {
						changedTokenNumber++
					}
					break
				}
			}
		}
		if changedTokenNumber != arg.expects.changedTokenNumber {
			t.Fatalf("Expected %d changed tokens, got %d", arg.expects.changedTokenNumber, changedTokenNumber)
		}
	}
	tests := []args{
		{
			name: "testDefault",
			arg: arg{
				accessKey:       "accessKey",
				secretKey:       "secretKey",
				beforeScrapes:   []configmaps.ScrapeConfig{},
				tenantName:      tenantAName,
				tenantNamespace: tenantANamespace,
				expects: expect{
					afterScrapeNumber: 1,
					afterScrapePath:   []string{"/minio/v2/metrics/cluster"},
					afterScrapeName: []string{
						fmt.Sprintf("%s-%s-minio-job-0", tenantAName, tenantANamespace),
					},
					changedTokenNumber: 0,
				},
			},
		},
		{
			name: "testFirst",
			arg: arg{
				accessKey:       "accessKey",
				secretKey:       "secretKey",
				paths:           []string{"/minio/v2/metrics/cluster", "/minio/metrics/v3/api"},
				beforeScrapes:   []configmaps.ScrapeConfig{},
				tenantName:      tenantAName,
				tenantNamespace: tenantANamespace,
				expects: expect{
					afterScrapeNumber: 2,
					afterScrapePath:   []string{"/minio/v2/metrics/cluster", "/minio/metrics/v3/api"},
					afterScrapeName: []string{
						fmt.Sprintf("%s-%s-minio-job-0", tenantAName, tenantANamespace),
						fmt.Sprintf("%s-%s-minio-job-1", tenantAName, tenantANamespace),
					},
					changedTokenNumber: 0,
				},
			},
		},
		{
			name: "testAlreadyHave",
			arg: arg{
				accessKey: "accessKey",
				secretKey: "secretKey",
				paths:     []string{"/minio/v2/metrics/cluster", "/minio/metrics/v3/api"},
				beforeScrapes: []configmaps.ScrapeConfig{
					{
						JobName: "testTarget",
						StaticConfigs: []configmaps.StaticConfig{
							{
								Targets: []string{"testTarget"},
							},
						},
					},
				},
				tenantName:      tenantAName,
				tenantNamespace: tenantANamespace,
				expects: expect{
					afterScrapeNumber: 3,
					afterScrapePath:   []string{"", "/minio/v2/metrics/cluster", "/minio/metrics/v3/api"},
					afterScrapeName: []string{
						"testTarget",
						fmt.Sprintf("%s-%s-minio-job-0", tenantAName, tenantANamespace),
						fmt.Sprintf("%s-%s-minio-job-1", tenantAName, tenantANamespace),
					},
					changedTokenNumber: 0,
				},
			},
		},
		{
			name: "testNoChange",
			arg: arg{
				accessKey: "accessKey",
				secretKey: "secretKey",
				paths:     []string{"/minio/v2/metrics/cluster", "/minio/metrics/v3/api"},
				beforeScrapes: []configmaps.ScrapeConfig{
					{
						JobName: "testTarget",
						StaticConfigs: []configmaps.StaticConfig{
							{
								Targets: []string{"testTarget"},
							},
						},
					},
					{
						JobName:     fmt.Sprintf("%s-%s-minio-job-0", tenantAName, tenantANamespace),
						BearerToken: (&miniov2.Tenant{}).GenBearerToken("accessKey", "secretKey"),
						MetricsPath: "/minio/v2/metrics/cluster",
						Scheme:      "https",
						TLSConfig: configmaps.TLSConfig{
							CAFile: "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt",
						},
						StaticConfigs: []configmaps.StaticConfig{
							{
								Targets: []string{fmt.Sprintf("minio.%s.svc.cluster.local:443", tenantANamespace)},
							},
						},
					},
					{
						JobName:     fmt.Sprintf("%s-%s-minio-job-1", tenantAName, tenantANamespace),
						BearerToken: (&miniov2.Tenant{}).GenBearerToken("accessKey", "secretKey"),
						MetricsPath: "/minio/metrics/v3/api",
						Scheme:      "https",
						TLSConfig: configmaps.TLSConfig{
							CAFile: "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt",
						},
						StaticConfigs: []configmaps.StaticConfig{
							{
								Targets: []string{fmt.Sprintf("minio.%s.svc.cluster.local:443", tenantANamespace)},
							},
						},
					},
				},
				tenantName:      tenantAName,
				tenantNamespace: tenantANamespace,
				expects: expect{
					afterScrapeNumber: 3,
					afterScrapePath:   []string{"", "/minio/v2/metrics/cluster", "/minio/metrics/v3/api"},
					afterScrapeName: []string{
						"testTarget",
						fmt.Sprintf("%s-%s-minio-job-0", tenantAName, tenantANamespace),
						fmt.Sprintf("%s-%s-minio-job-1", tenantAName, tenantANamespace),
					},
					changedTokenNumber: 0,
				},
			},
		},
		{
			name: "testPasswordChange",
			arg: arg{
				accessKey: "accessKeyChanged",
				secretKey: "secretKeyChanged",
				paths:     []string{"/minio/v2/metrics/cluster", "/minio/metrics/v3/api"},
				beforeScrapes: []configmaps.ScrapeConfig{
					{
						JobName: "testTarget",
						StaticConfigs: []configmaps.StaticConfig{
							{
								Targets: []string{"testTarget"},
							},
						},
					},
					{
						JobName:     fmt.Sprintf("%s-%s-minio-job-0", tenantAName, tenantANamespace),
						BearerToken: (&miniov2.Tenant{}).GenBearerToken("accessKey", "secretKey"),
						MetricsPath: "/minio/v2/metrics/cluster",
						Scheme:      "https",
						TLSConfig: configmaps.TLSConfig{
							CAFile: "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt",
						},
						StaticConfigs: []configmaps.StaticConfig{
							{
								Targets: []string{fmt.Sprintf("minio.%s.svc.cluster.local:443", tenantANamespace)},
							},
						},
					},
					{
						JobName:     fmt.Sprintf("%s-%s-minio-job-1", tenantAName, tenantANamespace),
						BearerToken: (&miniov2.Tenant{}).GenBearerToken("accessKey", "secretKey"),
						MetricsPath: "/minio/metrics/v3/api",
						Scheme:      "https",
						TLSConfig: configmaps.TLSConfig{
							CAFile: "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt",
						},
						StaticConfigs: []configmaps.StaticConfig{
							{
								Targets: []string{fmt.Sprintf("minio.%s.svc.cluster.local:443", tenantANamespace)},
							},
						},
					},
				},
				tenantName:      tenantAName,
				tenantNamespace: tenantANamespace,
				expects: expect{
					afterScrapeNumber: 3,
					afterScrapePath:   []string{"", "/minio/v2/metrics/cluster", "/minio/metrics/v3/api"},
					afterScrapeName: []string{
						"testTarget",
						fmt.Sprintf("%s-%s-minio-job-0", tenantAName, tenantANamespace),
						fmt.Sprintf("%s-%s-minio-job-1", tenantAName, tenantANamespace),
					},
					changedTokenNumber: 2,
				},
			},
		},
		{
			name: "testMultiTenantDifferentName",
			arg: arg{
				accessKey: "accessKey",
				secretKey: "secretKey",
				paths:     []string{"/minio/v2/metrics/cluster", "/minio/metrics/v3/api"},
				beforeScrapes: []configmaps.ScrapeConfig{
					{
						JobName: "testTarget",
						StaticConfigs: []configmaps.StaticConfig{
							{
								Targets: []string{"testTarget"},
							},
						},
					},
					{
						JobName:     fmt.Sprintf("%s-%s-minio-job-0", tenantAName, tenantANamespace),
						BearerToken: (&miniov2.Tenant{}).GenBearerToken("accessKey", "secretKey"),
						MetricsPath: "/minio/v2/metrics/cluster",
						Scheme:      "https",
						TLSConfig: configmaps.TLSConfig{
							CAFile: "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt",
						},
						StaticConfigs: []configmaps.StaticConfig{
							{
								Targets: []string{fmt.Sprintf("minio.%s.svc.cluster.local:443", tenantANamespace)},
							},
						},
					},
					{
						JobName:     fmt.Sprintf("%s-%s-minio-job-1", tenantAName, tenantANamespace),
						BearerToken: (&miniov2.Tenant{}).GenBearerToken("accessKey", "secretKey"),
						MetricsPath: "/minio/metrics/v3/api",
						Scheme:      "https",
						TLSConfig: configmaps.TLSConfig{
							CAFile: "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt",
						},
						StaticConfigs: []configmaps.StaticConfig{
							{
								Targets: []string{fmt.Sprintf("minio.%s.svc.cluster.local:443", tenantANamespace)},
							},
						},
					},
				},
				tenantName:      tenantBName,
				tenantNamespace: tenantBNamespace,
				expects: expect{
					afterScrapeNumber: 5,
					afterScrapePath:   []string{"", "/minio/v2/metrics/cluster", "/minio/metrics/v3/api", "/minio/v2/metrics/cluster", "/minio/metrics/v3/api"},
					afterScrapeName: []string{
						"testTarget",
						fmt.Sprintf("%s-%s-minio-job-0", tenantAName, tenantANamespace),
						fmt.Sprintf("%s-%s-minio-job-1", tenantAName, tenantANamespace),
						fmt.Sprintf("%s-%s-minio-job-0", tenantBName, tenantBNamespace),
						fmt.Sprintf("%s-%s-minio-job-1", tenantBName, tenantBNamespace),
					},
					changedTokenNumber: 0,
				},
			},
		},
		{
			name: "testMultiTenantSameName",
			arg: arg{
				accessKey: "accessKey",
				secretKey: "secretKey",
				paths:     []string{"/minio/v2/metrics/cluster", "/minio/metrics/v3/api"},
				beforeScrapes: []configmaps.ScrapeConfig{
					{
						JobName: "testTarget",
						StaticConfigs: []configmaps.StaticConfig{
							{
								Targets: []string{"testTarget"},
							},
						},
					},
					{
						JobName:     fmt.Sprintf("%s-%s-minio-job-0", tenantAName, tenantANamespace),
						BearerToken: (&miniov2.Tenant{}).GenBearerToken("accessKey", "secretKey"),
						MetricsPath: "/minio/v2/metrics/cluster",
						Scheme:      "https",
						TLSConfig: configmaps.TLSConfig{
							CAFile: "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt",
						},
						StaticConfigs: []configmaps.StaticConfig{
							{
								Targets: []string{fmt.Sprintf("minio.%s.svc.cluster.local:443", tenantANamespace)},
							},
						},
					},
					{
						JobName:     fmt.Sprintf("%s-%s-minio-job-1", tenantAName, tenantANamespace),
						BearerToken: (&miniov2.Tenant{}).GenBearerToken("accessKey", "secretKey"),
						MetricsPath: "/minio/metrics/v3/api",
						Scheme:      "https",
						TLSConfig: configmaps.TLSConfig{
							CAFile: "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt",
						},
						StaticConfigs: []configmaps.StaticConfig{
							{
								Targets: []string{fmt.Sprintf("minio.%s.svc.cluster.local:443", tenantANamespace)},
							},
						},
					},
				},
				tenantName:      tenantCName,
				tenantNamespace: tenantCNamespace,
				expects: expect{
					afterScrapeNumber: 5,
					afterScrapePath:   []string{"", "/minio/v2/metrics/cluster", "/minio/metrics/v3/api", "/minio/v2/metrics/cluster", "/minio/metrics/v3/api"},
					afterScrapeName: []string{
						"testTarget",
						fmt.Sprintf("%s-%s-minio-job-0", tenantAName, tenantANamespace),
						fmt.Sprintf("%s-%s-minio-job-1", tenantAName, tenantANamespace),
						fmt.Sprintf("%s-%s-minio-job-0", tenantCName, tenantCNamespace),
						fmt.Sprintf("%s-%s-minio-job-1", tenantCName, tenantCNamespace),
					},
					changedTokenNumber: 0,
				},
			},
		},
		{
			name: "testMultiTenantSameNameNoChange",
			arg: arg{
				accessKey: "accessKey",
				secretKey: "secretKey",
				paths:     []string{"/minio/v2/metrics/cluster", "/minio/metrics/v3/api"},
				beforeScrapes: []configmaps.ScrapeConfig{
					{
						JobName: "testTarget",
						StaticConfigs: []configmaps.StaticConfig{
							{
								Targets: []string{"testTarget"},
							},
						},
					},
					{
						JobName:     fmt.Sprintf("%s-%s-minio-job-0", tenantAName, tenantANamespace),
						BearerToken: (&miniov2.Tenant{}).GenBearerToken("accessKey", "secretKey"),
						MetricsPath: "/minio/v2/metrics/cluster",
						Scheme:      "https",
						TLSConfig: configmaps.TLSConfig{
							CAFile: "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt",
						},
						StaticConfigs: []configmaps.StaticConfig{
							{
								Targets: []string{fmt.Sprintf("minio.%s.svc.cluster.local:443", tenantANamespace)},
							},
						},
					},
					{
						JobName:     fmt.Sprintf("%s-%s-minio-job-1", tenantAName, tenantANamespace),
						BearerToken: (&miniov2.Tenant{}).GenBearerToken("accessKey", "secretKey"),
						MetricsPath: "/minio/metrics/v3/api",
						Scheme:      "https",
						TLSConfig: configmaps.TLSConfig{
							CAFile: "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt",
						},
						StaticConfigs: []configmaps.StaticConfig{
							{
								Targets: []string{fmt.Sprintf("minio.%s.svc.cluster.local:443", tenantANamespace)},
							},
						},
					},
					{
						JobName:     fmt.Sprintf("%s-%s-minio-job-0", tenantCName, tenantCNamespace),
						BearerToken: (&miniov2.Tenant{}).GenBearerToken("accessKey", "secretKey"),
						MetricsPath: "/minio/v2/metrics/cluster",
						Scheme:      "https",
						TLSConfig: configmaps.TLSConfig{
							CAFile: "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt",
						},
						StaticConfigs: []configmaps.StaticConfig{
							{
								Targets: []string{fmt.Sprintf("minio.%s.svc.cluster.local:443", tenantCNamespace)},
							},
						},
					},
					{
						JobName:     fmt.Sprintf("%s-%s-minio-job-1", tenantCName, tenantCNamespace),
						BearerToken: (&miniov2.Tenant{}).GenBearerToken("accessKey", "secretKey"),
						MetricsPath: "/minio/metrics/v3/api",
						Scheme:      "https",
						TLSConfig: configmaps.TLSConfig{
							CAFile: "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt",
						},
						StaticConfigs: []configmaps.StaticConfig{
							{
								Targets: []string{fmt.Sprintf("minio.%s.svc.cluster.local:443", tenantCNamespace)},
							},
						},
					},
				},
				tenantName:      tenantAName,
				tenantNamespace: tenantANamespace,
				expects: expect{
					afterScrapeNumber: 5,
					afterScrapePath:   []string{"", "/minio/v2/metrics/cluster", "/minio/metrics/v3/api", "/minio/v2/metrics/cluster", "/minio/metrics/v3/api"},
					afterScrapeName: []string{
						"testTarget",
						fmt.Sprintf("%s-%s-minio-job-0", tenantAName, tenantANamespace),
						fmt.Sprintf("%s-%s-minio-job-1", tenantAName, tenantANamespace),
						fmt.Sprintf("%s-%s-minio-job-0", tenantCName, tenantCNamespace),
						fmt.Sprintf("%s-%s-minio-job-1", tenantCName, tenantCNamespace),
					},
					changedTokenNumber: 0,
				},
			},
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			testRunCheckAndCreatePrometheusAddlConfig(t, tt.arg)
		})
	}
}
